Skip to content

feat: Major refactor adding tests and addressing found issues - #608

Merged
razor-x merged 20 commits into
mainfrom
claude/code-arch-srp-layers-gcnld8
Aug 6, 2026
Merged

feat: Major refactor adding tests and addressing found issues #608
razor-x merged 20 commits into
mainfrom
claude/code-arch-srp-layers-gcnld8

Conversation

@razor-x

@razor-x razor-x commented Aug 5, 2026

Copy link
Copy Markdown
Member
  • logout now deletes the per-server token key it stores tokens under,
    along with the legacy un-namespaced token and the current workspace.
  • getConfigStore() is memoized so reads no longer construct three
    Configstores and re-run the legacy migration on every call.
  • Remove unused get-current-workspace-id.ts.
  • Move the INSIDE_WEB_BROWSER read into env.ts so every environment
    variable is read in one place.

Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS

claude added 20 commits August 5, 2026 16:16
- logout now deletes the per-server token key it stores tokens under,
  along with the legacy un-namespaced token and the current workspace.
- getConfigStore() is memoized so reads no longer construct three
  Configstores and re-run the legacy migration on every call.
- Remove unused get-current-workspace-id.ts.
- Move the INSIDE_WEB_BROWSER read into env.ts so every environment
  variable is read in one place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
Pure moves with import updates only, no logic changes:

- util/cli-args.ts -> args/parse.ts (CLI argument definition + parsing)
- interact-for-*.ts -> interact/ (interactive prompting UX)
- render-help.ts -> render/help.ts, completion/ -> render/completion/
  (presentation renderers over the command spec)
- validate-token.ts -> auth/
- util/read-stdin-json.ts -> output/ (stdin/stdout/stderr layer)
- get-seam.ts -> seam/client.ts, util/request-seam-api.ts ->
  seam/request.ts (SDK init + request layer)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
- blueprint/source-npm.ts: fetch @seamapi/types from the npm registry,
  extract the OpenAPI module, and build a blueprint from it.
- blueprint/cache.ts: the on-disk blueprint cache with TTL and version
  invalidation, atomic writes, and blueprint-version discovery.
- blueprint/source-remote.ts: build a blueprint from the OpenAPI
  document served by the configured server.
- blueprint/index.ts: getApiBlueprint source selector (was
  get-api-blueprint.ts).
- blueprint/endpoint.ts: command path -> endpoint lookup and response
  key (was get-command-blueprint-def.ts + get-response-key.ts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
Types and helpers live with something real, not in grab-bag modules:

- util/prompt.ts -> interact/prompt.ts: the prompting primitive is the
  foundation of the interaction layer.
- util/with-loading.ts -> output/with-loading.ts: a stderr spinner
  gated on the output format is an output concern.
- util/ellipsis.ts -> render/text.ts: text truncation for display
  belongs to the presentation layer (markdown helpers join it later).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
Add context.ts with resolveAuth(): the server, token, and workspace are
resolved once with env-over-config precedence and tagged with their
source, replacing the per-value re-implementations in get-server.ts and
get-credentials.ts (both deleted, tests ported to context.test.ts as a
precedence table).

CliContext replaces ContextHelpers (types.ts deleted) and now carries
the config store and resolved auth alongside the blueprint and
interactivity. seam/client.ts takes an AuthContext instead of
re-resolving internally; blueprint/endpoint.ts narrows its dependency
to just the blueprint.

Config-mutating command bodies re-resolve after writes to keep today's
ordering semantics (login --server stores the token under the new
server's key).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
auth/operations.ts owns every auth/settings write: login, storeToken,
logout, selectServer, selectWorkspace, selectFakeServer, and
setUseRemoteApiDefs. The env-override policy now has one
implementation, assertMutable, driven by the AuthContext source tags —
the guards previously duplicated between the dispatcher and the
interact modules are gone.

The interact modules keep only prompting plus a call into operations;
the dispatcher branches shrink to selection and messaging. login()
stores the server before deriving the token key, covered by a unit
test on that ordering.

Behavior note: `config set fake-server` now clears the stored
workspace selection, matching `select server` — a workspace from the
previous server is not valid on the new one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
Adds TESTING.md and conforms the tests to it: ConfigStore interface + createMemoryConfigStore + setConfigStore slot; PromptClient set/reset slot + createMemoryPrompt; injected validate dependency on login(). The three tests using vi.mock module-path fakes are rewritten against injected fakes; no vi.mock substitution remains.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
"Seam" names the whole product, not a layer. This layer is the SDK/HTTP edge, so it takes the SDK package's name (@seamapi/http): http/client.ts constructs SeamHttp, http/request.ts makes the request. TESTING.md references updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
Command definitions previously lived in three places that had to agree
by hand: the localCommands array (help and completion), the if/else
dispatch chain in bin/cli.ts (execution), and a hardcoded list in the
interactive picker. They had already drifted: config set fake-server
existed only in the executor, wizard only in the spec.

commands/registry.ts is now the single source of truth. A Command
declares its definition, whether it needs a login, whether it is
hidden, and how to execute, so the spec, the picker, and the dispatcher
cannot disagree. Blueprint endpoints run through the generic
commands/api-command.ts executor, which also owns the per-endpoint
parameter policy and post-response follow-ups. bin/cli.ts shrinks to
parsing, the login gate, and a dispatch loop whose 'back' navigation
replaces the old self-recursion. assertKnownArgs moves to
args/validate.ts; toPlainText/firstSentence move to render/text.ts;
CliContext carries the output.

Visible changes: the interactive picker now offers wizard (it was in
help but missing from the picker), and config set fake-server loads the
cached API definitions like every other dispatched command.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
The only place required params were checked was inside the interactive
parameter editor, which is why validation lived in a UX module.
args/validate.ts now owns assertRequiredParams; the api-command executor
validates and sends directly on non-interactive runs, and the editor
delegates to the same function for its nested-object flows. Error
strings are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
Argument values previously arrived typed by minimist's guessing:
--is-managed true reached the API as the string "true" while the same
parameter entered interactively was a real boolean, opaque strings
were mangled into numbers unless hand-listed, and one list parameter
was comma-split by a hardcoded hack.

The api-command executor now re-reads argv with the endpoint's own
parameter types (string-listing everything that is not a number or
boolean) and args/coerce.ts turns each value into the JSON type its
parameter documents: real booleans and numbers, comma-split lists
typed per item, JSON-parsed objects, enum membership checked. A value
that does not fit fails with a UsageError naming what the parameter
expects instead of being sent for the API to reject.

BREAKING CHANGE: request bodies for arguments change type. Booleans
and lists that previously arrived as strings are now JSON booleans and
arrays; values outside a documented enum are rejected client-side.
Params piped in over stdin are passed through as given and are no
longer comma-split.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
http/api.ts declares SeamApi — post params to a path, read back status
and data — with createSeamApi() as the only place SeamHttp appears for
raw requests, and createMemorySeamApi() as its in-memory test fake: a
routes table plus a request capture, the in-process mirror of the e2e
suite's HTTP server.

requestSeamApi now takes its api and output as arguments, so the
error-status-to-exit-code behavior is covered by a classical test with
zero HTTP. Post-response follow-ups (connect webview open,
action-attempt poll) move to http/follow-ups.ts. CliContext gains a
lazy, per-run SeamApi accessor. UsageError and NonInteractiveError move
to errors.ts alongside reportErrorAndExit, the top-level error-to-exit
mapping formerly inlined in the entry's catch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
Fixtures live only in test/fixtures, and a test that consumes one is
not a unit test: the blueprint-driven suites (commands/spec,
commands/registry, render/help, render/completion) move under test/,
mirroring the source layout, along with every test that reaches into
another module for its fakes (context, auth/operations, http/request,
interact-for-blueprint-object).

A test now sits beside its module in src only when it tests the module
of the same name and imports nothing beyond it, external packages and
type-only imports excepted. TESTING.md records the rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
Where an interface has more than one implementation — the real edge
and its memory fake — each implementation is now a class, matching the
existing SeamConfigStore: SeamHttpApi and MemorySeamApi behind SeamApi,
TerminalPromptClient and MemoryPromptClient behind PromptClient,
MemoryConfigStore behind ConfigStore, and StreamOutput as the one
stream-parameterized Output. The createFoo factories remain as the
convenient constructors; call sites are unchanged apart from the memory
fakes now being the capture themselves (api.requests,
memoryPrompt.questions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
Awaited expressions no longer hide inside call arguments, casts, or
object literals — each await lands on its own line in a const, so a
rejection's stack trace points at a named step instead of an
expression soup.

getApiBlueprint(false, { update }) told a reader nothing about what
false meant; the selector now takes a single options object,
getApiBlueprint({ useRemoteDefinitions, update }). selectFakeServer
likewise takes { urlSeed, config } instead of a positional seed that
call sites passed as undefined.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
Parent-relative imports are now an error: the configured
import/no-relative-parent-imports rule was silently inert in this flat
config, so the core no-restricted-imports rule enforces it instead.
Every ../ import becomes a path alias — lib/* for source and a new
test/* alias for fixtures — resolved by tsconfig paths, vitest, and
tsc-alias in the build.

Seam is the default context, so it earns no place in names:
SeamConfigStore is really the PersistentConfigStore. The SeamApi port
keeps its name as the one thing genuinely named after the API, with
HttpSeamApi and MemorySeamApi as its implementations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
interact/interact-for-device.ts said interact three times before the
noun; the directory is now interactions/ and each file is just the
noun: interactions/device.ts, interactions/command-selection.ts, and
so on. The interactForFoo function names stay — they read as verbs at
the call site.

Likewise the create- prefix comes off module names: memory-output.ts,
memory-config-store.ts, memory-prompt.ts, and memory-seam-api.ts each
export their interface or class and its createFoo factory, and
create-output.ts is simply output.ts, home of the Output interface and
StreamOutput.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
interactions/ now holds nothing but the interactFor functions,
re-exported through index.ts, so consumers import from
lib/interactions/index.js without knowing the file-per-interaction
layout. The prompt framework powering them — PromptClient, the
terminal and memory clients, and the set/reset slot — moves out to sit
beside the directory as lib/prompt.ts and lib/memory-prompt.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
Adds eslint-import-resolver-typescript plus the import/parsers setting
so eslint-plugin-import can resolve .js-suffixed TypeScript imports and
parse the imported files — without both, its graph-based rules see
nothing and pass silently. import/no-cycle is now on (verified against
a deliberate cycle) and immediately caught a real one: config-store and
migrate imported each other, broken by extracting the shared pure
transforms into config/values.ts.

Two rules stay off, documented in place: import/extensions demands .ts
extensions once the resolver maps imports to their .ts files (nodenext
already fails the build on a bad extension), and
import/no-relative-parent-imports turns out to ban depending on parent
directories however the import is spelled — alias imports included —
which is not the specifier rule this repo wants; the core
no-restricted-imports pattern keeps doing that job.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
The port no longer imitates a transport with its own post method — it
prepares requests the way the SDK itself models them. SeamApi.createRequest
returns a SeamApiRequest (url, method, body, fetchResponse), and the real
implementation hands back an actual SeamHttpRequest, so the reported
request banner now shows the full resolved URL and error statuses arrive
as the SDK's typed SeamHttpApiError instead of hand-rolled status checks.
The memory fake rejects with those same SDK error classes, never an
imitation.

requestSeamApi returns the response body (null on an API error) and
renders the error payload from the typed error: type, message, and data.
Visible changes: the banner prints the full request URL rather than the
bare path, the informational [200] status line on success is gone, and a
non-Seam-shaped error response (e.g. proxy HTML) now reports as a CLI
error instead of being printed as a payload.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfGRqRqcApmECbDUkFuKfS
@razor-x
razor-x force-pushed the claude/code-arch-srp-layers-gcnld8 branch from e31c60d to 9ae1881 Compare August 5, 2026 16:50
@razor-x razor-x changed the title fix: Correct logout, memoize config store, remove dead code feat: Major refactor adding tests and addressing found issues Aug 6, 2026
@razor-x
razor-x merged commit 184d8f7 into main Aug 6, 2026
13 checks passed
@razor-x
razor-x deleted the claude/code-arch-srp-layers-gcnld8 branch August 6, 2026 03:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants